
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Jwaoo, Inc.
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_md5.h>
#include <ngx_redis.h>

#define AUTH_SENSE_DEBUG		0
#define AUTH_SENSE_OVERTIME		172800
#define AUTH_SENSE_USE_REDIS	1

#if AUTH_SENSE_DEBUG
#define pr_err_info(fmt, args ...) \
	fprintf(stderr, fmt "\n", ##args)
#else
#define pr_err_info(fmt, args ...)
#endif

#define pr_pos_info() \
	pr_err_info("%s(%s:%d)", __FUNCTION__, __FILE__, __LINE__)

#ifndef NELEM
#define NELEM(a) \
	(sizeof(a) / sizeof((a)[0]))
#endif

typedef struct {
    ngx_http_complex_value_t  *key;
} ngx_http_auth_sense_loc_conf_t;

static ngx_int_t ngx_http_auth_sense_handler(ngx_http_request_t *r);
static void *ngx_http_auth_sense_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_auth_sense_merge_loc_conf(ngx_conf_t *cf,
    void *parent, void *child);
static ngx_int_t ngx_http_auth_sense_init(ngx_conf_t *cf);


static ngx_command_t  ngx_http_auth_sense_commands[] = {

    { ngx_string("auth_sense"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
                        |NGX_CONF_TAKE1,
      ngx_http_set_complex_value_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_auth_sense_loc_conf_t, key),
      NULL },

      ngx_null_command
};


static ngx_http_module_t  ngx_http_auth_sense_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_auth_sense_init,              /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_auth_sense_create_loc_conf,   /* create location configuration */
    ngx_http_auth_sense_merge_loc_conf     /* merge location configuration */
};


ngx_module_t  ngx_http_auth_sense_module = {
    NGX_MODULE_V1,
    &ngx_http_auth_sense_module_ctx,       /* module context */
    ngx_http_auth_sense_commands,          /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};

#if AUTH_SENSE_USE_REDIS
static char *
ngx_http_auth_sense_get_query_string(ngx_http_request_t *r, ngx_str_t *key, char *token, int size)
{
	char *value;
	char *value_end;
	const char *name;
	const char *args = (char *) r->args.data;
	const char *args_end = args + r->args.len;

	value = NULL;
	value_end = token + size;

	for (name = args; args < args_end; args++) {
		switch (*args) {
		case '=':
			if (args - name != (int) key->len) {
				break;
			}

			if (strncmp((char *) key->data, name, key->len) != 0) {
				break;
			}

			value = token;
			break;

		case '&':
			if (value != NULL) {
				goto out_found;
			}

			name = args + 1;
			break;

		case 0:
		case ' ':
		case '\t':
		case '\r':
		case '\n':
		case '\f':
			goto out_complete;

		default:
			if (value != NULL) {
				if (value < value_end) {
					*value++ = *args;
				} else {
					return NULL;
				}
			}
		}
	}

out_complete:
	if (value == NULL) {
		return NULL;
	}
out_found:
	if (value < value_end) {
		*value = 0;
	}
	return value;
}
#else
static int
ngx_http_auth_sense_char2value(char c)
{
	switch (c) {
	case '0' ... '9':
		return c - '0';

	case 'a' ... 'f':
		return c - 'a' + 10;

	case 'A' ... 'F':
		return c - 'A' + 10;

	default:
		return -1;
	}
}

static ngx_int_t
ngx_http_auth_sense_check_timestamp(const ngx_str_t *timestamp)
{
	time_t time_req = atol((const char *) timestamp->data);
	time_t time_now = time(NULL);

	pr_err_info("time_req = %ld, time_now = %ld", time_req, time_now);

	if (time_now > time_req) {
		if (time_now - time_req > AUTH_SENSE_OVERTIME) {
			return NGX_ERROR;
		}
	} else if (time_req - time_now > AUTH_SENSE_OVERTIME) {
		return NGX_ERROR;
	}

	return NGX_OK;
}

static ngx_int_t
ngx_http_auth_sense_check_auth(const ngx_str_t *key, const ngx_str_t *auth, const ngx_str_t *timestamp)
{
	int         i;
	const char *p;
	ngx_md5_t   md5;
	u_char      buff[16];

	pr_err_info("key = %s", key->data);
	pr_err_info("auth = %s", auth->data);
	pr_err_info("timestamp = %s", timestamp->data);

	if (auth->len != sizeof(buff) * 2) {
		pr_err_info("invalid auth length: %ld", auth->len);
		return NGX_ERROR;
	}

	ngx_md5_init(&md5);
	ngx_md5_update(&md5, timestamp->data, timestamp->len);
	ngx_md5_update(&md5, key->data, key->len);
	ngx_md5_final(buff, &md5);

	for (i = 0, p = (char *) auth->data; i < (int) sizeof(buff); i++) {
		int value1 = ngx_http_auth_sense_char2value(*p++);
		int value0 = ngx_http_auth_sense_char2value(*p++);

		if (buff[i] != (value1 << 4 | value0)) {
			pr_err_info("md5sum mismatch!");
			return NGX_ERROR;
		}
	}

	return NGX_OK;
}
#endif

static ngx_int_t
ngx_http_auth_sense_handler(ngx_http_request_t *r)
{
    ngx_http_auth_sense_loc_conf_t      *alcf;
    ngx_str_t                            key;
#if AUTH_SENSE_USE_REDIS
	struct ngx_redis_client				*redis;
	char								token[128];
	int									token_len;
#else
    ngx_table_elt_t                     *timestamp = r->headers_in.timestamp;
#endif
	ngx_table_elt_t						*auth = r->headers_in.auth_sense;

	alcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_sense_module);
	if (alcf->key == NULL) {
		return NGX_DECLINED;
	}

	if (ngx_http_complex_value(r, alcf->key, &key) != NGX_OK) {
		return NGX_ERROR;
	}

	pr_err_info("key = %s", key.data);

#if AUTH_SENSE_USE_REDIS
	if (auth != NULL) {
		ngx_str_t *value = &auth->value;

		token_len = value->len;

		if (token_len < (int) sizeof(token)) {
			memcpy(token, value->data, token_len);
			token[token_len] = 0;
		} else {
			return NGX_ERROR;
		}
	} else {
		char *p = ngx_http_auth_sense_get_query_string(r, &key, token, sizeof(token));
		if (p == NULL) {
			pr_err_info("ngx_http_auth_sense_get_query_string");
			return NGX_HTTP_UNAUTHORIZED;
		}

		token_len = p - token;
	}

	pr_err_info("token = %s", token);

	redis = ngx_redis_simple_get();
	if (redis != NULL) {
		ngx_int_t status = NGX_HTTP_UNAUTHORIZED;
		ngx_http_core_main_conf_t *cmcf;
		ngx_str_t *uri = &r->uri;
		redisReply *reply;
		const char *argv[] = {
			"hexists", token, (char *) uri->data
		};
		const size_t argvl[] = {
			7, token_len, uri->len
		};

		cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
		if (cmcf != NULL) {
			if (cmcf->redis_host.len > 0) {
				redis->host = (char *) cmcf->redis_host.data;
			}

			if (cmcf->redis_port != NGX_CONF_UNSET_UINT) {
				redis->port = cmcf->redis_port;
			}
		}

		reply = ngx_redis_execute(redis, NELEM(argv), argv, argvl);
		if (reply != NULL) {
			if (reply->type == REDIS_REPLY_INTEGER && reply->integer > 0) {
				status = NGX_DECLINED;
			}

			freeReplyObject(reply);
		}

		ngx_redis_simple_put(redis);

		return status;
	}

	return NGX_HTTP_UNAUTHORIZED;
#else
	if (timestamp == NULL || ngx_http_auth_sense_check_timestamp(&timestamp->value) != NGX_OK) {
		pr_err_info("ngx_http_auth_sense_check_timestamp");
		return NGX_HTTP_UNAUTHORIZED;
	}

	if (auth == NULL || ngx_http_auth_sense_check_auth(&key, &auth->value, &timestamp->value) != NGX_OK) {
		pr_err_info("ngx_http_auth_sense_check_auth");
		return NGX_HTTP_UNAUTHORIZED;
	}

	return NGX_DECLINED;
#endif
}

static void *
ngx_http_auth_sense_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_auth_sense_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_sense_loc_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    return conf;
}


static char *
ngx_http_auth_sense_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_auth_sense_loc_conf_t  *prev = parent;
    ngx_http_auth_sense_loc_conf_t  *conf = child;

    if (conf->key == NULL) {
        conf->key = prev->key;
    }

    return NGX_CONF_OK;
}


static ngx_int_t
ngx_http_auth_sense_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_auth_sense_handler;

    return NGX_OK;
}
